/***********************************************************************************************************************
 * 
 * > QuadroCore <
 * 
 * Copyright (C) 2012 by Chris Channing
 *
 ***********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 ***********************************************************************************************************************
 * Notes:
 *
 *	The canvas is based on a Cartesian co-ordinate system. The point of origin is 0, 0 and is located in the bottom left
 *	hand corner of the graph.
 *
 ***********************************************************************************************************************/

#include "quadrocore.h"


#define NORMALIZE_ROW_BYTE_OFFSET(rowPos) ((((((rowPos / 8) + 1) * 8) - rowPos) - 1))

static LCDCANVASP lcdCanvasP = NULL;

void LCDCanvasDraw(uint8_t x, uint8_t y, uint8_t value);

BYTEP LCDCanvasGetRowByte(uint8_t x, uint8_t y);
byte LCDCanvasGetPageColumnByte(uint8_t pageStartPixelX, uint8_t pageStartPixelY);

LCDPixelMapping_t LCDCanvasSetPixel(uint8_t x, uint8_t y, uint8_t value);
uint8_t LCDCanvasGetPixel(uint8_t x, uint8_t y);

LCDPixelMapping_t LCDCanvasCalculatePixelMapping(uint8_t x, uint8_t y);

void LCDCanvasInit(bool_t directDraw)
{
	if (lcdCanvasP) return;
	
	lcdCanvasP = LCDCanvasAlloc(directDraw);
}

void LCDCanvasShutdown(void)
{
	if (! lcdCanvasP) return;
	
	LCDCanvasFree(lcdCanvasP);
	lcdCanvasP = NULL;
}

LCDCANVASP LCDCanvasAlloc(bool_t directDraw)
{
	LCDCANVASP ptr = calloc(1, sizeof(LCDCanvas_t));
	
	if (ptr)
	{
		ptr->bufferP = calloc(1, ((LCD_HEIGHT * LCD_WIDTH) / 8));
		ptr->directDraw = directDraw;
	}
	
	return ptr;
}

void LCDCanvasFree(LCDCANVASP lcdCanvasP)
{
	if (! lcdCanvasP) return;
	
	if (lcdCanvasP->bufferP)
	{
		free(lcdCanvasP->bufferP);
	}
	
	free(lcdCanvasP);
}

void LCDCanvasClear(void)
{
	for (uint8_t x = 0; x < LCD_WIDTH; x++)
	{
		for (uint8_t y = 0; y < LCD_HEIGHT; y++)
		{
			LCDCanvasClearPixel(x, y);
		}
	}
}

void LCDCanvasEnableDirectDraw(void)
{
	lcdCanvasP->directDraw = true;
}

void LCDCanvasDisableDirectDraw(void)
{
	lcdCanvasP->directDraw = false;
}

void LCDCanvasDraw(uint8_t x, uint8_t y, uint8_t value)
{
	if ((x < LCD_WIDTH) && (y < LCD_HEIGHT))
	{
		LCDPixelMapping_t lcdPixelMapping = LCDCanvasSetPixel(x, y, value);

		if (lcdCanvasP->directDraw)
		{
			LCDWriteCommand(LCD_CMD_PAGE_ADDRESS_SET(lcdPixelMapping.page));
			LCDWriteLongCommand(LCD_CMD_COLUMN_ADDRESS_SET_MSB((lcdPixelMapping.pageStartPixelX & 0xF0) >> 4), LCD_CMD_COLUMN_ADDRESS_SET_LSB((lcdPixelMapping.pageStartPixelX & 0x0F)));
			LCDWriteData(LCDCanvasGetPageColumnByte(lcdPixelMapping.pageStartPixelX, lcdPixelMapping.pageStartPixelY));	
		}			
	}		
}

void LCDCanvasDrawPixel(uint8_t x, uint8_t y)
{
	LCDCanvasDraw(x, y, 1);
}

void LCDCanvasClearPixel(uint8_t x, uint8_t y)
{
	LCDCanvasDraw(x, y, 0);
}

void LCDCanvasFlush(void)
{
	for (uint8_t page = 0; page < LCD_PAGES; page++)
	{
		uint8_t y = ((LCD_PAGES - page) * 8) - 1;
		
		LCDWriteCommand(LCD_CMD_PAGE_ADDRESS_SET(page));
		LCDWriteLongCommand(LCD_CMD_COLUMN_ADDRESS_SET_MSB(0), LCD_CMD_COLUMN_ADDRESS_SET_LSB(0));
		
		for (int x = 0; x < LCD_WIDTH; x++)
		{	
			LCDWriteData(LCDCanvasGetPageColumnByte(x, y));
		}
	}
}

BYTEP LCDCanvasGetRowByte(uint8_t x, uint8_t y)
{
	BYTEP byteP = NULL;
	
	if ((x < LCD_WIDTH) && (y < LCD_HEIGHT))
	{
		/**
			calculate the first row byte position relevant to y, then work out the distance from y to get the 
			actual byte using x. Note y will be in the range of 0 - (LCD_HEIGHT - 1), and x will be in the range
			0 - (LCD_WIDTH - 1)
		 */
		byteP = ((lcdCanvasP->bufferP + (y * LCD_ROW_WIDTH)) + (x / 8));
	}
	
	return byteP;
}

byte LCDCanvasGetPageColumnByte(uint8_t pageStartPixelX, uint8_t pageStartPixelY)
{
	byte value = 0;
	
	/*
		calculate the offsets into the buffer to fetch the pixel values used
		in the page for the given column. For example if the x,y pair falls in
		the middle of a column, we need the data above and below it. This 
		requires a correlation between the pixel buffer and the page/column
		system used in the LCD controller
	 */
	for (uint8_t count = 0; count < 8; count++, pageStartPixelY--)
	{
		uint8_t pixelValue = LCDCanvasGetPixel(pageStartPixelX, pageStartPixelY);
		value |= (pixelValue << count);
	}
			
	return value;
}

LCDPixelMapping_t LCDCanvasSetPixel(uint8_t x, uint8_t y, uint8_t value)
{
	LCDPixelMapping_t lcdPixelMapping;
	
	if ((x < LCD_WIDTH) && (y < LCD_HEIGHT))
	{
		BYTEP byteP = LCDCanvasGetRowByte(x, y);
	
		if (byteP)
		{
			/* 
				calculate byte count i.e. what byte x is in relative to y, then work out the total number of bits, deduct x and then deduct 1 
				to get the offset back to the correct 0 - n scale
			 */
			uint8_t offset = NORMALIZE_ROW_BYTE_OFFSET(x);
			*byteP &= ~(1 << offset);
			*byteP |= (value << offset);
		}
	
		lcdPixelMapping = LCDCanvasCalculatePixelMapping(x, y);
	}
	
	return lcdPixelMapping;		
}

uint8_t LCDCanvasGetPixel(uint8_t x, uint8_t y)
{
	uint8_t value = 0;
	
	if ((x < LCD_WIDTH) && (y < LCD_HEIGHT))
	{
		BYTEP byteP = LCDCanvasGetRowByte(x, y);
	
		if (byteP)
		{
			uint8_t offset = NORMALIZE_ROW_BYTE_OFFSET(x);
			value = ((*byteP >> offset) & 0x01);
		}
	}
				
	return value;
}

LCDPixelMapping_t LCDCanvasCalculatePixelMapping(uint8_t x, uint8_t y)
{
	LCDPixelMapping_t lcdPixelMapping;
	lcdPixelMapping.pixelX = x;
	lcdPixelMapping.pixelY = y;
	lcdPixelMapping.page = ((((LCD_PAGES * 8) - 1) - y) / 8);
	lcdPixelMapping.column = x;
	lcdPixelMapping.pageStartPixelY = ((LCD_PAGES - lcdPixelMapping.page) * 8) - 1;
	lcdPixelMapping.pageStartPixelX = x;
	
	return lcdPixelMapping;
}